<?php
/**
 * IO 复用，
 */

$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($sock === false) {
    return "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
}
echo "create tcp_socket OK \n";

$res = socket_bind($sock, '0.0.0.0', 9501);
if ($res === false) {
    return "socket_bind() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
}
echo "bind address OK \n";

$res = socket_listen($sock, 5);
if ($res === false) {
    return "socket_listen() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
}
echo "listen port OK \n";

// 客户端连接集合
$clients = [$sock];

while(true) {
    // socket_select() 的 $read、$write、$except 三个参数都是引用地址类型,
    // 所以需要将 $clients 赋值给 $reads, 保证 $reads 发生改动后不影响 $clients
    $reads = $clients;

    // 监视状态发生改变的连接（套接字）
    $res = socket_select($reads, $writes, $excepts, null);
    if ($res === false) {
        echo "socket_select() failed, reason: ". socket_strerror(socket_last_error()). "\n";
        continue;
    }

    // $sock 发生变化，说明有新的客户端连接
    if (in_array($sock, $reads)) {
        $newSock = socket_accept($sock);
        if ($newSock === false) {
            echo "socket_accept() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
        } else {
            // 将接收连接加入到 $clients 中
            $clients[] = $newSock;
            $num = count($clients) - 1;
            socket_write($newSock, "There are $num client(s) connected to the server\n");

            socket_getpeername($newSock, $ip);
            echo "New client connected: $ip\n";

            // $reads 包含“监听连接”和“接收连接”
            // 所以这里移除对 $sock(监听连接) 的监听
            // 只保留“接收连接”
            $key = array_search($sock, $reads);
            unset($reads[$key]);
        }
    }

    foreach ($reads as $readSock) {
        $buf = socket_read($readSock, 2048);
        if ($buf === false) {
            echo "socket_read() failed: reason: " . socket_strerror(socket_last_error($readSock)) . "\n";
            continue;
        }

        $buf = trim($buf);
        if (! $buf) {
            continue;
        }

        if ($buf === 'quit') {
            socket_close($readSock);
            $key = array_search($readSock, $clients);
            unset($clients[$key]);
            echo "client disconnected...\n";
            continue;
        }

        echo "$buf\n";

        // 给所有客户端连接发送消息，但要排除“监听连接”和自己（接收连接）
        foreach ($clients as $sendSock) {
            if ($sendSock === $sock || $sendSock === $readSock) {
                continue;
            }

            $talkback = "PHP: Others said '$buf'.\n";
            socket_write($sendSock, $talkback, strlen($talkback));

        } // 广播结束
    } // reading 结束
}